""" PyPlay is a simple media player that allows you to play
    songs from your hard drive in it 
    
    Authors: Jinyan Cao and Stefan Zhelyazkov
    CIS 192 Final Project
    2011
"""
 
import os
import string
import wx
import wx.media
import wx.lib.buttons as buttons
import urllib
import re
from cStringIO import StringIO
 
dirName = os.path.dirname(os.path.abspath(__file__))
bitmapDir = dirName + "/img"

### This is the player class where all of the content and functionality
### is defined

class Player(wx.Panel):
    
    """ Definition of the PyPlayer """
    
    def __init__(self, parent):
        """Creates a PyPlay object instance"""
        wx.Panel.__init__(self, parent=parent)
        
        # Saves a dictionary of all songs
        # SongName -> SongLocation
        self.songlist = {}
        
        # the parent is the main frame
        self.frame = parent
        self.currentVolume = 50
        self.createMenu()
        self.layoutControls()
        
        # StandartdPaths gets reference to the local directory
        allpaths = wx.StandardPaths.Get()
        self.currentFolder = allpaths.GetDocumentsDir()
        
        # the timer starts the application
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.onTimer)
        self.timer.Start(100)
 
    #----------------------------------------------------------------------
    def layoutControls(self):
        """Creates the media player object"""
        
        # wx.media.MediaCtrl creates the media player object
        try:
            self.mediaPlayer = wx.media.MediaCtrl(self, style=wx.SIMPLE_BORDER)
        except NotImplementedError:
            self.Destroy()
 
        # create playback slider
        self.playbackSlider = wx.Slider(self, size=wx.DefaultSize, style=wx.SL_HORIZONTAL)
        self.Bind(wx.EVT_SLIDER, self.onSeek, self.playbackSlider)
 
        self.volumeCtrl = wx.Slider(self, style=wx.SL_HORIZONTAL)
        self.volumeCtrl.SetRange(0, 100)
        self.volumeCtrl.SetValue(self.currentVolume)
        self.volumeCtrl.Bind(wx.EVT_SLIDER, self.onSetVolume)
        
        #         TOP SIZER
        # ------------------------
        # |           |          |
        # | mainSizer | PlayList |
        # |           |          |
        # ------------------------
        topSizer = wx.BoxSizer(wx.HORIZONTAL)
        # main sizer is the main layout arrangement
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        # hSizer is the audioSizer + volume control
        hSizer = wx.BoxSizer(wx.HORIZONTAL)
        # image is the image viewer
        image = wx.BoxSizer(wx.HORIZONTAL)
        # audioSizer is the buttons
        audioSizer = self.buildAudioBar()
        # listbox
        self.listbox = wx.ListBox(self, size=(270, 230), 
                                  choices=self.songlist.keys())
        # triggers event on left mouse double click
        self.listbox.Bind(wx.EVT_LEFT_DCLICK, self.onPlaylist)
        
        # creates an image viewer
        img = wx.Image("img/blank.jpg", wx.BITMAP_TYPE_JPEG)
        self.imageCtrl = wx.StaticBitmap(self, wx.ID_ANY, 
                                         wx.BitmapFromImage(img), size=(160,160))
        image.Add(self.imageCtrl, proportion=1)
 
        # layout widgets
        # wx.ALL = all sides put border; 
        # wx.CENTER = center it
        mainSizer.Add(self.playbackSlider, 0, wx.ALL|wx.EXPAND, 5)
        hSizer.Add(audioSizer, 0, wx.ALL|wx.CENTER, 5)
        hSizer.Add(self.volumeCtrl, 0, wx.ALL, 5)
        
        mainSizer.Add(image, 0, wx.ALL|wx.CENTER)
        mainSizer.Add(hSizer)
        
        topSizer.Add(mainSizer)
        topSizer.Add(self.listbox)

        self.SetSizer(topSizer)
        self.Layout()
 
    #----------------------------------------------------------------------
    def buildAudioBar(self):
        """
        Builds the audio bar controls
        """
        
        # this is the orientation of the menu with the buttons
        audioBarSizer = wx.BoxSizer(wx.HORIZONTAL)
 
        self.buildBtn({'bitmap':'player_prev.png', 'handler':self.onPrev,
                       'name':'prev'},
                      audioBarSizer)
 
        # create play/pause toggle button
        img = wx.Bitmap(os.path.join(bitmapDir, "player_play.png"))
        self.playPauseBtn = buttons.GenBitmapToggleButton(self, bitmap=img, name="play")
        self.playPauseBtn.Enable(False)
 
        img = wx.Bitmap(os.path.join(bitmapDir, "player_pause.png"))
        self.playPauseBtn.SetBitmapSelected(img)
        self.playPauseBtn.SetInitialSize()
 
        self.playPauseBtn.Bind(wx.EVT_BUTTON, self.onPlay)
        audioBarSizer.Add(self.playPauseBtn, 0, wx.LEFT, 3)
 
        btnData = [{'bitmap':'player_stop.png',
                    'handler':self.onStop, 'name':'stop'},
                    {'bitmap':'player_next.png',
                     'handler':self.onNext, 'name':'next'}]
        for btn in btnData:
            self.buildBtn(btn, audioBarSizer)
 
        return audioBarSizer
 
    #----------------------------------------------------------------------
    def buildBtn(self, btnDict, sizer):
        """
        Builds a button
        """
        bmp = btnDict['bitmap']
        handler = btnDict['handler']
 
        img = wx.Bitmap(os.path.join(bitmapDir, bmp))
        btn = buttons.GenBitmapButton(self, bitmap=img, name=btnDict['name'])
        btn.SetInitialSize()
        btn.Bind(wx.EVT_BUTTON, handler)
        sizer.Add(btn, 0, wx.LEFT, 3)
 
    #----------------------------------------------------------------------
    def createMenu(self):
        """Creates the menu bar"""
        menubar = wx.MenuBar()
        
        # creates the categories for the menu
        file = wx.Menu()
        help = wx.Menu()
        
        # append items to the first category
        open = wx.MenuItem(file, 101, "&Open", "Open a new file")
        open.SetBitmap(wx.Image('img/open_file.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap())
        file.AppendItem(open)
        addfolder = wx.MenuItem(file, 102, "&AddFolder", "Add a folder to player")
        addfolder.SetBitmap(wx.Image('img/add_folder.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap())
        file.AppendItem(addfolder)
        file.AppendSeparator()
        quit = wx.MenuItem(file, 105, '&Quit\tCtrl+Q', 'Quit the Application')
        quit.SetBitmap(wx.Image('img/stock_exit.png',wx.BITMAP_TYPE_PNG).ConvertToBitmap())
        file.AppendItem(quit)
        
        # append items to the second category
        get_help = wx.MenuItem(help, 201, "&Help", "Get Help")
        get_help.SetBitmap(wx.Image('img/help.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap())
        help.AppendItem(get_help)
        about = wx.MenuItem(help, 202, "&About", "About us")
        about.SetBitmap(wx.Image('img/about.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap())
        help.AppendItem(about)
        menubar.Append(file, '&File') # name of menu tab
        menubar.Append(help, "&Help") # name of menu tab
        
        # set the menu bar and attach the buttons
        self.frame.SetMenuBar(menubar)
        self.frame.Bind(wx.EVT_MENU, self.onBrowse, id=101)
        self.frame.Bind(wx.EVT_MENU, self.onAddFolder, id=102)
        self.frame.Bind(wx.EVT_MENU, self.onQuit, id=105)
        self.frame.Bind(wx.EVT_MENU, self.onHelp, id=201)
        self.frame.Bind(wx.EVT_MENU, self.onAbout, id=202)
        
    #----------------------------------------------------------------------
    
    def extractSong(self, location):
        """Extracts the name of the song from the given location"""
        
        # try to get the name on UNIX system
        song = string.split(location, "/")[-1]
        # give it a try on a Windows system
        song = string.split(song, "\\")[-1]
        songmatch = re.compile("([0-9]*\.)*(.*?)\.(mp3|MP3|wav|WAV)")
        song = songmatch.match(song)
        song = song.group(2)
        return song    
        
    #----------------------------------------------------------------------
    def loadMusic(self, musicFile):
        """
        Load the music into the MediaCtrl or display an error dialog
        if the user tries to load an unsupported file type
        """
        if not self.mediaPlayer.Load(musicFile):
            wx.MessageBox("Unable to load %s: Unsupported format?" % path,
                          "ERROR",
                          wx.ICON_ERROR | wx.OK)
        else:
            self.mediaPlayer.SetInitialSize()
            self.GetSizer().Layout()
            self.playbackSlider.SetRange(0, self.mediaPlayer.Length())
            self.playPauseBtn.Enable(True)
            self.mediaPlayer.Play()
            
            song = self.extractSong(musicFile)
            self.songlist[song] = musicFile
            picmatch = re.compile("img class=\"sg_i\" src=\"(.*?)\" style")
            url = 'http://www.bing.com/images/search?q=' + song + '&FORM=BIFD'
            
            # Fetches and image from the web
            try:
                urlhandle = urllib.urlopen(url)
                for line in urlhandle:
                    pic = picmatch.search(line)
                    if pic is not None:
                        self.pic = pic.group(1)
                        break
                urlhandle.close()
            except IOError as (errno, strerror):
                print "The given url cannot be opened"
            
            # Updates the Image Viewer
            try:    
                image_url = urllib.urlopen(self.pic)
                data = image_url.read()
                image_url.close()
                img = wx.ImageFromStream(StringIO(data))
                self.imageCtrl.SetBitmap(wx.BitmapFromImage(img))
            except IOError as (errno, strerror):
                print "The program cannot open location"
    
    #---------------------------------------------------------------------            
    def onPlayPrev(self, event):
        """
        Plays a previously stored music file
        """
        label = event.GetEventObject().GetLabel()
        
        for song in self.songlist.keys():
            if song.name == label:
                self.loadMusic(song.path)
                break

    #--------------------------------------------------------------------
    def onAddFolder(self, event):
        """
        Adds a whole folder to music player
        """
        
        dlg = wx.DirDialog(
            self, message="Choose a folder",
            defaultPath=self.currentFolder,
            style=wx.OPEN | wx.CHANGE_DIR
            )
        if dlg.ShowModal() == wx.ID_OK:
            # navigate through folder and find all songs in folder
            path = dlg.GetPath()
            self.findallsongs(path)
            self.currentFolder = os.path.dirname(path)
                
        dlg.Destroy()
        
    #---------------------------------------------------------------------
    def findallsongs(self, path):
        """ 
        Recursively find all songs in a given directory
        """
        wild = re.compile(r".*?((.MP3)|(.mp3)|(.wav)|(.WAV))$")
        for file in os.listdir(path):
            filename = os.path.join(path, file)
            # go recursively for folders inside folder
            if os.path.isdir(filename):
                self.findallsongs(filename)
            # if a file instead, add it to the library
            else:
                if re.match(wild, filename):
                    # getting the names of all songs
                    self.songlist[self.extractSong(filename)] = filename
        self.updateSongList()
        
    #---------------------------------------------------------------------
    def onBrowse(self, event):
        """
        Opens file dialog to browse for music
        """
        wildcard = "MP3 (*.mp3)|*.mp3|"     \
                   "WAV (*.wav)|*.wav"
        dlg = wx.FileDialog(
            self, message="Choose a file",
            defaultDir=self.currentFolder,
            defaultFile="",
            wildcard=wildcard,
            style=wx.OPEN | wx.CHANGE_DIR
            )
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            # file contains the name of the file
            file = dlg.GetFilenames().pop(0)
            file = self.extractSong(file)
            self.songlist[file] = path
            
            self.file = file
            self.currentFolder = os.path.dirname(path)
            self.loadMusic(path)
        
        self.updateSongList()
        dlg.Destroy()
        
    # -----------------------------------------------------------------
    def updateSongList(self):
        """Updates the songlist"""
        added = sorted(self.songlist.keys())
        self.listbox.Set(added)
        
    #------------------------------------------------------------------
    def onPlaylist(self, event):
        """Gets the song from the playlist"""
        self.mediaPlayer.Stop()
        self.playPauseBtn.SetToggle(False)
        num = self.listbox.GetSelection()
        self.file = sorted(self.songlist.keys())[num]
        location = self.songlist[sorted(self.songlist.keys())[num]]
        self.loadMusic(location)
        
    # -----------------------------------------------------------------
    def onQuit(self, event):
        """Quits"""
        self.frame.Close()
    
    #------------------------------------------------------------------
    def onHelp(self, event):
        """Help Menu"""
        msg = "Use this media player on Windows platform, please.\n" + \
                "The GUI is not formatted for UNIX systems.\n\n" + \
                "You should press Play button every time you change song via next/prev "+ \
                "or select through songlist\n\n" + \
                "Please contact Stefan (stz@seas.upenn.edu) or Jin (jinyan@seas.upenn.edu) if any additional " + \
                "bugs are detected, thank you!\n\n" + \
                "P.S. ignore the song image if the image doesn't make sense, that's Bing's fault, not our fault :]"
        
        dlg = wx.MessageDialog(None, msg, 'Help', wx.OK)
        dlg.ShowModal()
        dlg.Destroy()
    
    #------------------------------------------------------------------
    def onAbout(self, event):
        """Launches the about screen"""
        msg = "This is the about dialog of PyPlay.\n\n" + \
        "Authors: Stefan Zhelyazkov and Jinyan Cao\n\n" + \
        "Please report any bugs/requests of improvements\n" + \
        "to us at the following addresses:\n\n" + \
        "stz@seas.upenn.edu\n" + "jinyan@seas.upenn.edu\n\n" + \
        "Welcome to PyPlay 1.0!\n\n" + \
        "April 2011"
        
        dlg = wx.MessageDialog(None,msg,'About PyPlay',wx.OK)
        dlg.ShowModal()
        dlg.Destroy()
 
    # ----------------------------------------------------------------------
    def onNext(self, event):
        """ Goes to next song on track list"""
        index = -1
        sortedlist = sorted(self.songlist.keys())
        for song in self.songlist.keys():
            if song == self.file:
                index = sortedlist.index(song)
                index = index + 1
                break
        if index < len(self.songlist) and index >= 0:
            self.mediaPlayer.Stop()
            self.playPauseBtn.SetToggle(False)
            self.file = sortedlist[index]
            num = sorted(self.songlist.keys()).index(self.file)
            self.listbox.SetSelection(num)
            self.loadMusic(self.songlist[sortedlist[index]])  
 
    #----------------------------------------------------------------------
    def onPause(self):
        """Pauses the music"""
        self.mediaPlayer.Pause()
 
    #----------------------------------------------------------------------
    def onPlay(self, event):
        """Plays the music"""
        
        # if the play button is unpressed, pause
        if not event.GetIsDown():
            self.onPause()
            return
        
        # if the music cannot be played, throw some error
        if not self.mediaPlayer.Play():
            wx.MessageBox("The song cannot be played. Are you playing MP3?",
                          "ERROR", wx.ICON_ERROR | wx.OK)
        else:
            self.mediaPlayer.SetInitialSize()
            self.GetSizer().Layout()
            self.playbackSlider.SetRange(0, self.mediaPlayer.Length())
 
        event.Skip()
 
    #----------------------------------------------------------------------
    def onPrev(self, event):
        """need to work on this one"""
        index = -1
        sortedlist = sorted(self.songlist.keys())
        for song in self.songlist.keys():
            if song == self.file:
                index = sortedlist.index(song)
                index = index - 1
                break
        if index < len(self.songlist) and index >= 0:
            self.mediaPlayer.Stop()
            self.playPauseBtn.SetToggle(False)
            self.file = sortedlist[index]
            num = sorted(self.songlist.keys()).index(self.file)
            self.listbox.SetSelection(num)
            self.loadMusic(self.songlist[sortedlist[index]])
            
    
    #----------------------------------------------------------------------
    def onSeek(self, event):
        """Seeks the media file according to the amount the slider has
            been adjusted."""
        offset = self.playbackSlider.GetValue()
        self.mediaPlayer.Seek(offset)
 
    #----------------------------------------------------------------------
    def onSetVolume(self, event):
        """Sets the volume of the music player"""
        """for some reason does not work right"""
        self.currentVolume = self.volumeCtrl.GetValue()
        self.mediaPlayer.SetVolume(self.currentVolume / 100.0)
 
    #----------------------------------------------------------------------
    def onStop(self, event):
        """Stops the music and resets the play button"""
        self.mediaPlayer.Stop()
        self.playPauseBtn.SetToggle(False)
 
    #----------------------------------------------------------------------
    def onTimer(self, event):
        """Keeps the player slider updated"""
        offset = self.mediaPlayer.Tell()
        self.playbackSlider.SetValue(offset)
 

### This is the main frame that shows up on the screen when the player is
### launched

class MainFrame(wx.Frame):
 
    """Instance of this class instantiates
        an empty window with title PyPlay"""
    def __init__(self):
        # Wx.Frame inherits from Wx.Window
        wx.Frame.__init__(self, None, wx.ID_ANY, "PyPlay")
        panel = Player(self)
 

### Run the program

if __name__ == "__main__":
    # App() creates the application
    app = wx.App()
    # creates the window
    frame = MainFrame()
    frame.Center()
    frame.SetSize((620, 300))
    frame.SetIcon(wx.Icon('img/icon_play.png', wx.BITMAP_TYPE_PNG))
    # shows the window to the world
    frame.Show()
    # enters a loop in which it listens to events
    app.MainLoop()
